/*
 * XReflectProxy.java
 * 
 * Copyright (c) 2006 - 2007
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.xfc.util.reflect;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * This class allows callers to create Proxy implementations that automatically dispatch to themselves
 * based on method names. For example, a XReflectProxy with the following methods:
 * <p>
 * <code><pre>
 *   void foo()
 * 
 *   String bar(Object one, int two)
 * </code></pre>
 * 
 * would be automatically receive calls from an interface supporting similar methods. This class allows
 * the user to avoid messy string comparisons on the method name in invoke(), or other similar
 * hand-rolled dispatch techniques. Any proxy calls which are not resolved successfully via reflection
 * are dispatched to the unhandled(String, Object[]) method.
 * <p>
 * In the above example, the bar() method would receive dispatched from both <code>bar(Object one, int two)</code>
 * and <code>bar(String one, int two)</code> interface methods. This allows proxy implementations to not
 * include all interface related classes at compile time.
 * 
 * @author Devon Carew
 */
public class XReflectProxy
	implements InvocationHandler
{
	
	/**
	 * Create a proxy object supporting the given interface and dispatching to the given XReflectProxy.
	 * 
	 * @param interfaceClass
	 * @param handler
	 * @return Returns a proxy object supporting the given interface and dispatching to the given
	 *         XReflectProxy.
	 */
	public static Object createProxyObject(Class interfaceClass, XReflectProxy handler)
	{
		return Proxy.newProxyInstance(
			interfaceClass.getClassLoader(), new Class[] { interfaceClass }, handler);
	}
	
	/**
	 * Create a proxy object supporting the given interfaces and dispatching to the given XReflectProxy.
	 * 
	 * @param interfaceClasses
	 * @param handler
	 * @return Returns a proxy object supporting the given interface and dispatching to the given
	 *         XReflectProxy.
	 */
	public static Object createProxyObject(Class[] interfaceClasses, XReflectProxy handler)
	{
		if (interfaceClasses.length == 0)
			throw new IllegalArgumentException("interfaceClasses may not be empty");
		
		return Proxy.newProxyInstance(
			interfaceClasses[0].getClassLoader(), interfaceClasses, handler);
	}
	
	
	public final Object invoke(Object proxy, Method method, Object[] args)
	{
		String methodName = method.getName();
		
		Method dispatchMethod = XReflectClass.lookupMethod(getClass(), methodName, args, false);
		
		if (dispatchMethod != null)
		{
			try
	        {
	            return dispatchMethod.invoke(this, args);
	        }
	        catch (Throwable t)
	        {
	            if (t instanceof RuntimeException)
	                throw (RuntimeException)t;
	            
	            throw new RuntimeException(t);
	        }
		}
		else
		{
			return unhandled(methodName, args);
		}
	}
	
	
	/**
	 * This method is called if a suitable method is not found by reflecting on this subclass of
	 * XReflectProxy.
	 * 
	 * @param methodName
	 * @param args
	 * 
	 * @return Returns the result to pass to the proxy's caller.
	 */
	protected Object unhandled(String methodName, Object[] args)
	{
		
		return null;
	}
	
}
